//
// Copyright (c) 2010 All Right Reserved
//
// vl
//
// 2010-05-01
// Contains ...
using System;
using System.Diagnostics.Contracts;
using System.IO;
using System.Linq;
using System.Text;
namespace LargoCommon.Midi
{
///
/// Midi File.
///
public class MidiFile {
#region Constructors
///
/// Initializes a new instance of the class.
///
/// The file path.
public MidiFile(string filePath) {
Contract.Requires(this.FilePath != null);
//// Validate input
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(this.FilePath), "No path provided.");
}
this.FilePath = filePath;
this.Name = Path.GetFileNameWithoutExtension(this.FilePath.Trim());
this.Import();
}
///
/// Initializes a new instance of the class.
///
/// The file path.
/// The given sequence.
/// FilePath - No path provided.
public MidiFile(string filePath, CompactMidiStrip givenSequence)
{
Contract.Requires(this.FilePath != null);
//// Validate input
if (string.IsNullOrEmpty(filePath)) {
throw new ArgumentNullException(nameof(this.FilePath), "No path provided.");
}
this.FilePath = filePath;
this.Name = Path.GetFileNameWithoutExtension(this.FilePath.Trim());
this.Sequence = givenSequence;
}
#endregion
#region Properties
///
/// Gets or sets the name.
///
///
/// The name.
///
public string Name { get; set; }
///
/// Gets or sets the file path.
///
///
/// The file path.
///
public string FilePath { get; set; }
///
/// Gets or sets the sequence.
///
///
/// The sequence.
///
public CompactMidiStrip Sequence { get; set; }
///
/// Gets a value indicating whether this instance has valid sequence.
///
///
/// True if this instance has valid sequence; otherwise, false.
///
public bool HasValidSequence => this.Sequence != null && this.Sequence.Count > 0;
#endregion
#region String representation
/// String representation of the object.
/// Returns value.
public override string ToString() {
var s = new StringBuilder();
s.AppendFormat("Path: {0}", this.FilePath);
return s.ToString();
}
#endregion
///
/// Creates a MIDI file at the specified path and writes the sequence to it.
///
public void Save()
{
Contract.Requires(this.Sequence != null);
// Create the output file and save to it
using (var stream = new FileStream(this.FilePath, FileMode.Create)) {
this.Save(stream);
}
}
#region Private methods - Importing from MIDI file
///
/// Reads a MIDI stream into a new CompactMidiStrip.
///
private void Import()
{
//// Open the file
//// try {
//// using (FileStream inputStream = new FileStream(filePath, FileMode.Open)) {
using (var inputStream = new FileStream(this.FilePath, FileMode.Open, FileAccess.Read, FileShare.Read)) {
// Parse it and return the sequence
this.Import(inputStream); //// Avoid multiple or conditional return statements.
}
//// }
//// IOException
//// UnauthorizedAccessException
}
///
/// Reads a MIDI stream into a new CompactMidiStrip.
///
/// The stream containing the MIDI data.
/// Input Stream Exception.
/// Can't read the MIDI file. - 0
/// or
/// Invalid MIDI header. - 0
private void Import(Stream inputStream)
{
//// Validate input
if (inputStream == null) {
throw new ArgumentNullException(nameof(inputStream));
}
if (!inputStream.CanRead) {
throw new MidiParserException("Can't read the MIDI file.", 0);
}
//// Read in the main MIDI header
var mainHeader = MidiFileChunkHeader.Read(inputStream);
if (mainHeader.NumberOfLines < 0) {
throw new MidiParserException("Invalid MIDI header.", 0);
}
//// Read in all of the tracks
var trackChunks = new MidiTrackChunkHeader[mainHeader.NumberOfLines];
for (var i = 0; i < mainHeader.NumberOfLines; i++) {
trackChunks[i] = MidiTrackChunkHeader.Read(inputStream);
}
// Create the MIDI sequence
this.Sequence = new CompactMidiStrip(mainHeader.Format, mainHeader.Division)
{
Header =
{
Name = this.Name,
//// FilePath = this.FilePath,
FileName = Path.GetFileNameWithoutExtension(this.FilePath)
}
};
for (var i = 0; i < mainHeader.NumberOfLines; i++) {
var data = trackChunks[i].GetData();
if (data == null) {
continue;
}
var parser = new MidiParser(data, null);
this.Sequence.AddTrack(parser.ParseToTrack());
}
this.Sequence.SetTrackNamesFromMeta();
//// 2015/01
if (this.Sequence.Count > 0) {
this.Sequence.Header.Metric = this.Sequence[0].Metric;
}
//// sequence.SetMetricFromTracks();
//// sequence.SetTracksMetric();
this.Sequence.SetTrackInstrumentsFromFirstOccurrence();
}
#endregion
#region Private mothods - Exporting to MIDI file
///
/// Writes a MIDI file header out to the stream.
///
/// The output stream.
/// This functionality is automatically performed during a Save.
private void WriteHeader(Stream outputStream)
{
// Check parameters
if (this.Sequence == null) {
throw new ArgumentNullException(nameof(this.Sequence));
}
if (outputStream == null) {
throw new ArgumentNullException(nameof(outputStream));
}
if (!outputStream.CanWrite) {
throw new MidiParserException("Can't write to stream.", 0);
}
if (this.Sequence.Count < 1) {
throw new ArgumentOutOfRangeException(nameof(this.Sequence), this.Sequence.Count, "Sequences require at least 1 track.");
}
// Write out the main header for the sequence
var mainHeader = new MidiFileChunkHeader(this.Sequence.Format, this.Sequence.Count, this.Sequence.Header.Division);
mainHeader.Write(outputStream);
}
///
/// Writes the MIDI sequence to the output stream.
///
/// The stream to which the MIDI sequence should be written.
private void Save(Stream outputStream)
{
//// Contract.Requires(sequence != null);
//// Contract.Requires(outputStream != null);
//// Check valid state (as best we can check)
if (this.Sequence == null || this.Sequence.Count < 1) {
throw new InvalidOperationException("Empty sequence (no musical tracks have been added).");
}
//// Check parameters
if (outputStream == null) {
throw new ArgumentNullException(nameof(outputStream));
}
if (!outputStream.CanWrite) {
throw new ArgumentException("Can't write to stream.", nameof(outputStream));
}
// Write out the main header for the sequence
this.WriteHeader(outputStream);
// Write out each track in the order it was added to the sequence
foreach (var track in this.Sequence.Where(track => track != null)) {
track.Write(outputStream);
}
//// DoNotCatchGeneralExceptionTypes try { catch (Exception exc) .. "Unable to save the sequence to a local file."
}
#endregion
}
}